package com.gitee.wsl.security.hash

import com.gitee.wsl.ext.array.bytes.toByteArray
import com.gitee.wsl.ext.array.toByteArray
import com.gitee.wsl.ext.array.bytes.toIntArray
import com.gitee.wsl.ext.base.Arrays.arraycopy
import com.gitee.wsl.lang.ByteOrder

class SM3Digest {

    /** 缓冲区  */
    private val xBuf = ByteArray(BUFFER_LENGTH)

    /** 缓冲区偏移量  */
    private var xBufOff = 0

    /** 初始向量  */
    private var V = SM3.iv.clone()

    private var cntBlock = 0

    constructor()

    constructor(t: SM3Digest) {
        arraycopy(t.xBuf, 0, this.xBuf, 0, t.xBuf.size)
        this.xBufOff = t.xBufOff
        arraycopy(t.V, 0, this.V, 0, t.V.size)
    }

    /**
     * SM3结果输出
     *
     * @param out 保存SM3结构的缓冲区
     * @param outOff 缓冲区偏移量
     * @return
     */
    fun doFinal(out: ByteArray, outOff: Int): Int {
        val tmp = doFinal()
        arraycopy(tmp, 0, out, 0, tmp.size)
        return digestSize
    }

    fun reset() {
        xBufOff = 0
        cntBlock = 0
        V = SM3.iv.clone()
    }

    /**
     * 明文输入
     *
     * @param in
     * 明文输入缓冲区
     * @param inOff
     * 缓冲区偏移量
     * @param len
     * 明文长度
     */
    fun update(`in`: ByteArray, inOff: Int, len: Int) {
        val partLen = BUFFER_LENGTH - xBufOff
        var inputLen = len
        var dPos = inOff
        if (partLen < inputLen) {
            arraycopy(`in`, dPos, xBuf, xBufOff, partLen)
            inputLen -= partLen
            dPos += partLen
            doUpdate()
            while (inputLen > BUFFER_LENGTH) {
                arraycopy(`in`, dPos, xBuf, 0, BUFFER_LENGTH)
                inputLen -= BUFFER_LENGTH
                dPos += BUFFER_LENGTH
                doUpdate()
            }
        }

        arraycopy(`in`, dPos, xBuf, xBufOff, inputLen)
        xBufOff += inputLen
    }

    private fun doUpdate() {
        val B = ByteArray(BLOCK_LENGTH)
        var i = 0
        while (i < BUFFER_LENGTH) {
            arraycopy(xBuf, i, B, 0, B.size)
            doHash(B)
            i += BLOCK_LENGTH
        }
        xBufOff = 0
    }

    private fun doHash(B: ByteArray) {
        val tmp = CF(V, B)
        arraycopy(tmp, 0, V, 0, V.size)
        cntBlock++
    }

    private fun CF(iv: ByteArray,word: ByteArray): ByteArray
        = SM3.CF(iv.toIntArray(ByteOrder.BigEndian), word.toIntArray(ByteOrder.BigEndian)).toByteArray(ByteOrder.BigEndian)

    private fun doFinal(): ByteArray {
        val B = ByteArray(BLOCK_LENGTH)
        val buffer = ByteArray(xBufOff)
        arraycopy(xBuf, 0, buffer, 0, buffer.size)
        val tmp = padding(buffer, cntBlock)
        var i = 0
        while (i < tmp.size) {
            arraycopy(tmp, i, B, 0, B.size)
            doHash(B)
            i += BLOCK_LENGTH
        }
        return V
    }

    fun update(`in`: Byte) {
        val buffer = byteArrayOf(`in`)
        update(buffer, 0, 1)
    }


    companion object {

        /** SM3值的长度  */
        const val digestSize: Int = 32

        /** SM3分组长度  */
        private const val BLOCK_LENGTH = 64

        /** 缓冲区长度  */
        private val BUFFER_LENGTH = BLOCK_LENGTH * 1


        /**
         * 对最后一个分组字节数据padding
         *
         * @param in
         * @param bLen
         * 分组个数
         * @return
         */
        fun padding(`in`: ByteArray, bLen: Int): ByteArray {
            var k = 448 - (8 * `in`.size + 1) % 512
            if (k < 0) {
                k = 960 - (8 * `in`.size + 1) % 512
            }
            k += 1
            val padd = ByteArray(k / 8)
            padd[0] = 0x80.toByte()
            val n = (`in`.size * 8 + bLen * 512).toLong()
            val out = ByteArray(`in`.size + k / 8 + 64 / 8)
            var pos = 0
            arraycopy(`in`, 0, out, 0, `in`.size)
            pos += `in`.size
            arraycopy(padd, 0, out, pos, padd.size)
            pos += padd.size
            val tmp: ByteArray = back(n.toByteArray())
            arraycopy(tmp, 0, out, pos, tmp.size)
            return out
        }

        /**
         * 字节数组逆序
         *
         * @param in
         * @return
         */
        private fun back(`in`: ByteArray): ByteArray {
            val out = ByteArray(`in`.size)
            for (i in out.indices) {
                out[i] = `in`[out.size - i - 1]
            }
            return out
        }
    }
}